/**
 *  Copyright 2002 Quaseem
 *  Copyright 2004 Migisan
 *  Copyright 2008 Oest
 *
 *  This file is part of the aochat package.
 *
 *  The aochat package is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  The aochat package is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with the aochat package; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package sk.sigp.aobot.client.core.message.general;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;

import sk.sigp.aobot.client.types.ChatGroupId;
import sk.sigp.aobot.client.types.GroupId;
import sk.sigp.aobot.client.types.Int;
import sk.sigp.aobot.client.types.Map;
import sk.sigp.aobot.client.types.Raw;
import sk.sigp.aobot.client.types.Text;
import sk.sigp.aobot.client.types.UserId;
import sk.sigp.aobot.client.types.general.AbstractType;

public class Message implements Serializable {
	// Definition of messages from the client.

	private static final long serialVersionUID = 1L;

	public static final int M_C_LOGIN_RESPONSE = 0;
	public static final int M_C_LOGIN_RESPONSE_NICK = 1;
	public static final int M_C_LOGIN_RESPONSE_GETCHARACTERLIST = 2;
	public static final int M_C_LOGIN_SELECT_CHARACTER = 3;
	public static final int M_C_NAME_LOOKUP = 21;
	public static final int M_C_MESSAGE_PRIVATE = 30;
	public static final int M_C_BUDDY_ADD = 40;
	public static final int M_C_BUDDY_REMOVE = 41;
	public static final int M_C_ONLINESTATUS_SET = 42;
	public static final int M_C_PRIVATEGROUP_INVITE = 50;
	public static final int M_C_PRIVATEGROUP_KICK = 51;
	public static final int M_C_PRIVATEGROUP_JOIN = 52;
	public static final int M_C_PRIVATEGROUP_PART = 53;
	public static final int M_C_PRIVATEGROUP_KICKALL = 54;
	public static final int M_C_PRIVATEGROUP_MESSAGE = 57;
	public static final int M_C_GROUP_DATA_SET = 64;
	public static final int M_C_GROUP_MESSAGE = 65;
	public static final int M_C_GROUP_CLIENTMODE_SET = 66;
	public static final int M_C_CLIENTMODE_GET = 70;
	public static final int M_C_CLIENTMODE_SET = 71;
	public static final int M_C_PING = 100;
	public static final int M_C_CC = 120;

	// Definition of messages from the server.

	public static final int M_S_LOGIN_SEED = 0;
	public static final int M_S_LOGIN_RESULT_OK = 5;
	public static final int M_S_LOGIN_RESULT_ERROR = 6;
	public static final int M_S_LOGIN_CHARACTERLIST = 7;
	public static final int M_S_CLIENT_UNKNOWN = 10;
	public static final int M_S_CLIENT_NAME = 20;
	public static final int M_S_NAME_LOOKUP_RESULT = 21;
	public static final int M_S_MESSAGE_PRIVATE = 30;
	public static final int M_S_MESSAGE_VICINITY = 34;
	public static final int M_S_MESSAGE_ANON_VICINITY = 35;
	public static final int M_S_MESSAGE_SYSTEM = 36;
	public static final int M_S_CHAT_NOTICE = 37;
	public static final int M_S_BUDDY_ADDED = 40;
	public static final int M_S_BUDDY_REMOVED = 41;
	public static final int M_S_PRIVATEGROUP_INVITED = 50;
	public static final int M_S_PRIVATEGROUP_KICKED = 51;
	public static final int M_S_PRIVATEGROUP_PART = 53;
	public static final int M_S_PRIVATEGROUP_CLIENT_JOINED = 55;
	public static final int M_S_PRIVATEGROUP_CLIENT_PARTED = 56;
	public static final int M_S_PRIVATEGROUP_MESSAGE = 57;
	public static final int M_S_GROUP_JOIN = 60;
	public static final int M_S_GROUP_PART = 61;
	public static final int M_S_GROUP_MESSAGE = 65;
	public static final int M_S_PONG = 100;
	public static final int M_S_FORWARD = 110;
	public static final int M_S_ADM_MUX_INFO = 1100;

	// Some constants used.

	public static final int CHAR_STATUS_OFFLINE = 0;
	public static final int CHAR_STATUS_ONLINE = 1;
	public static final int CHAR_STATUS_AWAY = 2;
	public static final int CHAR_STATUS_EXTENDEDAWAY = 3;
	public static final int GROUP_USERFLAG_IGNORED = 0x00010000;
	public static final int GROUP_USERFLAG_USER_ISMUTED = 0x01000000;
	public static final int GROUP_USERFLAG_USER_ISLOGGING = 0x02000000;
	public static final int GROUP_USERFLAG_MASK = 0xffff0000;

	public static final int M_SERVER = 0;
	public static final int M_CLIENT = 1;

	public static final String encoding = "UTF-8";

	protected int messageType;
	protected Object[] mydata;
	protected int messageDirection;
	public long producedTime;

	public Message(int messageType, DataInputStream input, int length)
			throws IOException {
		this.messageType = messageType;
		messageDirection = M_SERVER;
		this.producedTime = System.currentTimeMillis();
		if (getMessageFormat() != null) {
			mydata = new Object[getMessageFormat().length];
			parseStream(input, length);
		} else {
			mydata = new Object[0];
		}

	}

	public Message(int messageType) {
		this.messageType = messageType;
		messageDirection = M_CLIENT;
		this.producedTime = System.currentTimeMillis();
		mydata = new Object[getMessageFormat().length];
	}

	/**
	 * Maps message type to message format (as an array of classes).
	 */
	@SuppressWarnings("unchecked")
	public static Class[] getClientMessageFormat(int messageType) {
		switch (messageType) {
		case M_C_LOGIN_RESPONSE:
			return new Class[] { Int.class, Int.class, Text.class, Text.class };
		case M_C_LOGIN_RESPONSE_NICK:
			return new Class[] { Int.class, Text.class, Text.class, Text.class };
		case M_C_LOGIN_RESPONSE_GETCHARACTERLIST:
			return new Class[] { Int.class, Text.class, Text.class };
		case M_C_LOGIN_SELECT_CHARACTER:
			return new Class[] { UserId.class };
		case M_C_NAME_LOOKUP:
			return new Class[] { Text.class };
		case M_C_MESSAGE_PRIVATE:
			return new Class[] { UserId.class, Text.class, Raw.class };
		case M_C_BUDDY_ADD:
			return new Class[] { UserId.class, Raw.class };
		case M_C_BUDDY_REMOVE:
			return new Class[] { UserId.class };
		case M_C_ONLINESTATUS_SET:
			return new Class[] { Int.class };
		case M_C_PRIVATEGROUP_INVITE:
			return new Class[] { UserId.class };
		case M_C_PRIVATEGROUP_KICK:
			return new Class[] { UserId.class };
		case M_C_PRIVATEGROUP_JOIN:
			return new Class[] { ChatGroupId.class };
		case M_C_PRIVATEGROUP_PART:
			return new Class[] { ChatGroupId.class };
		case M_C_PRIVATEGROUP_KICKALL:
			return new Class[] {};
		case M_C_PRIVATEGROUP_MESSAGE:
			return new Class[] { ChatGroupId.class, Text.class, Raw.class };
		case M_C_GROUP_DATA_SET:
			return new Class[] { GroupId.class, Int.class, Raw.class };
		case M_C_GROUP_MESSAGE:
			return new Class[] { GroupId.class, Text.class, Raw.class };
		case M_C_GROUP_CLIENTMODE_SET:
			return new Class[] { GroupId.class, Int.class, Int.class,
					Int.class, Int.class };
		case M_C_CLIENTMODE_GET:
			return new Class[] { Int.class, GroupId.class };
		case M_C_CLIENTMODE_SET:
			return new Class[] { Int.class, Int.class, Int.class, Int.class };
		case M_C_PING:
			return new Class[] { Raw.class };
		case M_C_CC:
			return new Class[] { Text[].class };
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public static Class[] getServerMessageFormat(int messageType) {
		switch (messageType) {
		case M_S_LOGIN_SEED:
			return new Class[] { Text.class };
		case M_S_LOGIN_RESULT_OK:
			return new Class[] {};
		case M_S_LOGIN_RESULT_ERROR:
			return new Class[] { Text.class };
		case M_S_LOGIN_CHARACTERLIST:
			return new Class[] { UserId[].class, Text[].class, Int[].class,
					Int[].class };
		case M_S_CLIENT_UNKNOWN:
			return new Class[] { Int.class };
		case M_S_CLIENT_NAME:
			return new Class[] { UserId.class, Text.class };
		case M_S_NAME_LOOKUP_RESULT:
			return new Class[] { UserId.class, Text.class };
		case M_S_MESSAGE_PRIVATE:
			return new Class[] { UserId.class, Text.class, Raw.class };
		case M_S_MESSAGE_VICINITY:
			return null; // ??????
		case M_S_MESSAGE_ANON_VICINITY:
			return new Class[] { Text.class, Text.class, Raw.class };
		case M_S_MESSAGE_SYSTEM:
			return new Class[] { Text.class };
		case M_S_BUDDY_ADDED:
			return new Class[] { UserId.class, Int.class, Raw.class };
		case M_S_BUDDY_REMOVED:
			return new Class[] { UserId.class };
		case M_S_PRIVATEGROUP_INVITED:
			return new Class[] { ChatGroupId.class };
		case M_S_PRIVATEGROUP_KICKED:
			return new Class[] { ChatGroupId.class };
		case M_S_PRIVATEGROUP_PART:
			return new Class[] { ChatGroupId.class };
		case M_S_PRIVATEGROUP_CLIENT_JOINED:
			return new Class[] { ChatGroupId.class, UserId.class };
		case M_S_PRIVATEGROUP_CLIENT_PARTED:
			return new Class[] { ChatGroupId.class, UserId.class };
		case M_S_PRIVATEGROUP_MESSAGE:
			return new Class[] { ChatGroupId.class, UserId.class, Text.class,
					Raw.class };
		case M_S_GROUP_JOIN:
			return new Class[] { GroupId.class, Text.class, Int.class,
					Raw.class };
		case M_S_GROUP_PART:
			return new Class[] { GroupId.class };
		case M_S_GROUP_MESSAGE:
			return new Class[] { GroupId.class, UserId.class, Text.class,
					Raw.class };
		case M_S_PONG:
			return new Class[] { Raw.class };
		case M_S_FORWARD:
			return new Class[] { UserId.class, Map.class };
		case M_S_CHAT_NOTICE:
			return new Class[] { Int.class, Int.class, Int.class, Text.class };
		case M_S_ADM_MUX_INFO:
			return new Class[] { Int[].class, Int[].class, Int[].class };
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public Class[] getMessageFormat() {
		if (messageDirection == M_CLIENT)
			return getClientMessageFormat(getMessageType());
		else
			return getServerMessageFormat(getMessageType());
	}

	public int getMessageType() {
		return messageType;
	}

	public Object getArrayAt(int i) {
		return (Object) mydata[i];
	}

	public AbstractType getElementAt(int i) {
		return (AbstractType) mydata[i];
	}

	public int getIntAt(int i) {
		return getElementAt(i).getIntData();
	}

	public long getLongAt(int i) {
		return getElementAt(i).getLongData();
	}

	public String getStringAt(int i) {
		return getElementAt(i).getStringData();
	}

	public void setElementAt(int i, AbstractType d) {
		mydata[i] = d;
	}

	@SuppressWarnings("unchecked")
	protected void parseStream(DataInputStream input, int length)
			throws IOException, ClassCastException {
		Class[] msgFormat = getMessageFormat();
		for (int j = 0; j < msgFormat.length; j++) {
			int size;
			if (msgFormat[j] == Int.class) {
				mydata[j] = new Int(input);
			} else if (msgFormat[j] == Int[].class) {
				size = input.readUnsignedShort();
				Int[] intArr = new Int[size];
				for (int i = 0; i < size; i++) {
					intArr[i] = new Int(input);
				}
				mydata[j] = intArr;
			} else if (msgFormat[j] == Text.class) {
				mydata[j] = new Text(input);
			} else if (msgFormat[j] == Text[].class) {
				size = input.readUnsignedShort();
				Text strArr[] = new Text[size];
				for (int i = 0; i < size; i++) {
					strArr[i] = new Text(input);
				}
				mydata[j] = strArr;
			} else if (msgFormat[j] == Raw.class) {
				mydata[j] = new Raw(input);
			} else if (msgFormat[j] == UserId.class) {
				mydata[j] = new UserId(input);
			} else if (msgFormat[j] == UserId[].class) {
				size = input.readUnsignedShort();
				UserId[] intArr = new UserId[size];
				for (int i = 0; i < size; i++) {
					intArr[i] = new UserId(input);
				}
				mydata[j] = intArr;
			} else if (msgFormat[j] == ChatGroupId.class) {
				mydata[j] = new ChatGroupId(input);
			} else if (msgFormat[j] == GroupId.class) {
				mydata[j] = new GroupId(input);
			} else if (msgFormat[j] == Map.class) {
				mydata[j] = new Map(input);
			}
			/**/
			else {
				System.err
						.println("unbekannter Datentyp in message.ParseStream: "
								+ msgFormat[j]);
			}
		}
	}

	public String toString() {
		char cr = 10;
		String str = "messageType=" + messageType;
		for (int i = 0; i < mydata.length; i++) {
			str += cr + "        data[" + i + "]=";
			if (mydata[i].getClass().equals(Int[].class)) {
				Int[] arr = (Int[]) mydata[i];
				str += "ao_int[" + arr.length + "]=( ";
				for (int j = 0; j < arr.length; j++)
					if (j == 0)
						str += arr[j].getIntData();
					else
						str += "; " + arr[j].getIntData();
				str += " )";
			} else if (mydata[i].getClass().equals(UserId[].class)) {
				UserId[] arr = (UserId[]) mydata[i];
				str += "ao_userID[" + arr.length + "]=( ";
				for (int j = 0; j < arr.length; j++)
					if (j == 0)
						str += arr[j].getIntData();
					else
						str += "; " + arr[j].getIntData();
				str += " )";
			} else if (mydata[i].getClass().equals(Text[].class)) {
				char c = 34;
				Text[] arr = (Text[]) mydata[i];
				str += "ao_text[" + arr.length + "]=( ";
				for (int j = 0; j < arr.length; j++)
					if (j == 0)
						str += c + arr[j].getStringData() + c;
					else
						str += "; " + c + arr[j].getStringData() + c;
				str += " )";
			} else
				str += mydata[i].toString();
		}
		return str;
	}

	@SuppressWarnings("unchecked")
	public void write(DataOutputStream output) throws IOException,
			UnsupportedEncodingException {
		if (messageType == M_C_MESSAGE_PRIVATE
				|| messageType == M_C_PRIVATEGROUP_MESSAGE
				|| messageType == M_C_GROUP_MESSAGE) {
			Raw raw = (Raw) mydata[2];
			if (raw != null) {
				switch (messageType) {
				case M_C_MESSAGE_PRIVATE:
					mydata[2] = new Raw(new byte[] { 0, 0 });
					((Raw) mydata[2]).append(raw);
					break;
				default:
					mydata[2] = new Raw(new byte[] { 0 });
					((Raw) mydata[2]).append(raw);
				}
			}
		}
		output.writeShort(getMessageType());
		// Find size of message
		int msgSize = 0;
		Class[] msgFormat = getMessageFormat();
		for (int j = 0; j < msgFormat.length; j++) {
			Object data = getElementAt(j);
			if (msgFormat[j] == Int[].class) {
				msgSize += ((Int[]) data).length * 4 + 2;
			} else if (msgFormat[j] == Text[].class) {
				msgSize += 2;
				Text[] strs = (Text[]) data;
				for (int i = 0; i < strs.length; i++)
					msgSize += strs[i].size();
			} else {
				msgSize += ((AbstractType) data).size();
			}
		}

		output.writeShort(msgSize);

		for (int j = 0; j < msgFormat.length; j++) {
			Object data = getElementAt(j);
			if (msgFormat[j] == Int[].class) {
				Int[] ints = (Int[]) data;
				output.writeShort(ints.length);
				for (int i = 0; i < ints.length; i++)
					ints[i].write(output);
			} else if (msgFormat[j] == Text[].class) {
				Text[] strs = (Text[]) data;
				output.writeShort(strs.length);
				for (int i = 0; i < strs.length; i++)
					strs[i].write(output);
			} else {
				((AbstractType) data).write(output);
			}
		}
		output.flush();
	}
}
